/**
 * 
 */
package org.statewalker.core.server;

import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.statewalker.core.Core;
import org.statewalker.core.IState;
import org.statewalker.core.IStateFactory;
import org.statewalker.core.ProcessDescriptor;
import org.statewalker.fsm.config.FsmConfigParser;
import org.statewalker.fsm.providers.FsmTransitionsRegistry;

/**
 * This class is used to configure {@link Core} instances with process
 * descriptions. This class loads process descriptions from XML files and
 * registers transitions between process states in the {@link Core}. It also
 * loads and instantiates handler factories for individual states. To load
 * handler classes this object uses a user-specified class loader.
 * 
 * @author kotelnikov
 */
public class ServerCoreConfigurator {

    /**
     * The internal logger.
     */
    private final static Logger log = Logger
        .getLogger(ServerCoreConfigurator.class.getName());

    /**
     * This class loader is used to load state handlers.
     */
    private ClassLoader fLoader;

    /**
     * The default constructor;
     * 
     * @param loader the class loader used to load state handlers
     */
    public ServerCoreConfigurator(ClassLoader loader) {
        fLoader = loader;
    }

    /**
     * This method reads an XML description of the process from the given stream
     * and adds the process description to the specified {@link Core} instance.
     * This method loads and registers transitions between process states as
     * well as the corresponding state handlers.
     * 
     * @param core the core to configure
     * @param processName the name of the process to configure
     * @param config the input stream containing the XML description of the
     *        process
     * @throws Exception
     */
    public void configureCore(Core core, String processName, InputStream config)
        throws Exception {
        try {
            ProcessDescriptor descriptor = core
                .newProcessDescriptor(processName);
            Map<String, String> properties = Collections.emptyMap();
            IStateFactory defaultStateFactory = newStateFactory(
                ServerSyncState.class,
                properties);
            descriptor.setDefaultFactory(defaultStateFactory);

            // ---------------------------------------------------------------------
            // Transitions between sub-states
            FsmTransitionsRegistry registry = descriptor.addNewRegistry();
            FsmConfigParser parser = new FsmConfigParser(registry);
            parser.parse(config);

            Set<String> stateKeys = registry.getStateKeys();
            for (String stateKey : stateKeys) {
                properties = registry.getProperties(stateKey);
                IStateFactory factory = getStateFactory(stateKey, properties);
                descriptor.setStateFactory(stateKey, factory);
                String processorKey = getProcessorKey(stateKey, properties);
                if (processorKey != null) {
                    descriptor.setProcessorKey(stateKey, processorKey);
                }
            }
        } finally {
            config.close();
        }
    }

    /**
     * Detects and returns the key of the processor associated with the
     * specified state.
     * 
     * @param stateKey the key of the state
     * @param properties a list of state properties
     * @return the key of the processor associated with the specified state
     */
    protected String getProcessorKey(
        String stateKey,
        Map<String, String> properties) {
        String processorKey = properties != null
            ? properties.get("processor")
            : null;
        return processorKey;
    }

    /**
     * Creates and returns a factory for handlers associated with the specified
     * state. This method uses the name of the class defined in the state
     * configuration.
     * 
     * @param stateKey the key of the state
     * @param properties a list of state properties
     * @return a factory generating handlers for states with the specified key
     */
    @SuppressWarnings("unchecked")
    private IStateFactory getStateFactory(
        String stateKey,
        Map<String, String> properties) {
        IStateFactory factory = null;
        try {
            if (properties != null) {
                String handlerClassName = properties.get("handler");
                if (handlerClassName != null) {
                    Class<?> cls = fLoader.loadClass(handlerClassName);
                    if (IState.class.isAssignableFrom(cls)) {
                        Class<? extends IState> c = (Class<? extends IState>) cls;
                        factory = newStateFactory(c, properties);
                    }
                }
            }
        } catch (Exception e) {
            // Do nothing. Just it means that the state does not have
            // specific handlers.
            log.log(Level.WARNING, "Can not load a state handler. StateKey: '"
                + stateKey
                + "'. Handler class: '"
                + properties
                + "'.", e);
        }
        return factory;
    }

    /**
     * Creates and returns a handler factory for the specified handler type.
     * 
     * @param c the type (class) of the state handler
     * @param properties a list of properties associated with states of this
     *        type
     * @return a handler factory generating handlers for states with the
     *         specified key
     * @throws NoSuchMethodException
     */
    protected IStateFactory newStateFactory(
        Class<? extends IState> c,
        Map<String, String> properties) throws NoSuchMethodException {
        return new ClassBasedStateFactory(c, properties);
    }
}